Skip to content

feat(fork): P0b — 场景 A worktree + WIP carry-over#252

Merged
lishuceo merged 5 commits into
mainfrom
feat/p0b-worktree-fork
May 27, 2026
Merged

feat(fork): P0b — 场景 A worktree + WIP carry-over#252
lishuceo merged 5 commits into
mainfrom
feat/p0b-worktree-fork

Conversation

@lishuceo
Copy link
Copy Markdown
Owner

Summary

  • 场景 A 实装:父话题已 setup_workspace 时,fork 用 `git worktree add` 给子话题创建独立工作树,基于父 HEAD 切新分支 `-fork-`
  • 默认继承父 WIP(staged + unstaged + untracked):用 `git stash push -u` → 子 `stash apply` → 父 `stash pop` 路径(`stash create` 不支持 `-u`,只能 push+pop)
  • `/fork --clean` opt-out:跳过 WIP 继承,子从纯 HEAD 起步
  • 删除 `FORK_ALLOWED_USERS` 重复白名单;`FORK_ENABLED` 默认开启
  • 失败按 JSONL → worktree → branch 顺序回滚;最危险分支(子 apply 成功但父 pop 失败)写 CRITICAL 日志提示手动恢复

风险说明

父工作树有亚秒级"清空"窗口(`stash push` 之后、`pop` 之前)。/fork 是用户同步触发的,期间父话题 Claude idle 不写文件,可接受。

Test plan

  • `npx vitest run src/session/tests/fork.test.ts` — 13/13 通过(含 5 个新场景 A case)
  • `npm run typecheck` clean
  • `npm run lint` 无新 warning
  • 全量 `npx vitest run` — 1624/1625 通过(memory quality 失败与 fork 无关,依赖外部 LLM)
  • 手工:在已 setup_workspace 的话题里 `/fork 验证` 看父子工作目录隔离,`/fork --clean` 看 WIP 不传递

lishuceo and others added 4 commits May 27, 2026 18:36
- 解析 /fork [--clean] [描述],--clean 透传给 forkSession
- 删除 FORK_ALLOWED_USERS 白名单(devbot 已有 ALLOWED_USER_IDS,重复)
- FORK_ENABLED 默认开启,改为 != 'false' 才关

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
P0a 之前对已 setup_workspace 的话题直接拒绝。P0b 实装:
- 在父 git 仓库上 'git worktree add -b <parent>-fork-<shortId>'
- 默认继承父 WIP: stash push -u → 子 stash apply → 父 stash pop
  (git stash create 不支持 -u,只能走 push+pop 路径)
- /fork --clean 跳过 WIP 继承,子从纯 HEAD 起步
- 失败时按 JSONL → worktree → branch 顺序回滚
- 新增 reason 码 worktree_create_failed / stash_apply_failed

最危险分支:子 apply 成功但父 pop 失败 → 父 WIP 卡在 stash@{0},日志
CRITICAL 提示用户手动 'git stash pop' 恢复。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
5 个新 case 在 tmpdir 中 git init 真实仓库:
- 默认: staged + unstaged + untracked 全继承,父工作树 status 不变
- --clean: 子是纯 HEAD,父 WIP 不传递
- 父无 WIP: stash push 不创建条目,子干净,不报错
- worktree_create_failed: 父非 git 仓库 → 干净回滚,无 JSONL/分支泄漏
- feishu 失败: worktree + branch + JSONL 都被清理

删除 scenario_a_not_supported_yet 旧 case(reason 已移除)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
git stash create 不支持 -u(选项被当 message),无法捕获 untracked。
实装必须走 push --include-untracked → 子 apply → 父 pop 路径,
父工作树有亚秒级清空窗口(fork 同步,父 Claude idle,可接受)。
更新最危险分支说明。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

Claude finished @lishuceo's task in 2m 10s —— View job


Code Review — ⚠️ Issues Found (2 minor)

  • Check previous review comments (none from claude[bot])
  • Read CLAUDE.md and PR context
  • Review src/session/fork.ts
  • Review src/session/__tests__/fork.test.ts
  • Review src/feishu/event-handler.ts and src/config.ts
  • Post inline comments + summary

总体评价

场景 A 的实现整体扎实。Worktree+stash push -u+pop 的方案是务实的选择(stash create 不支持 -u 这点 PR 描述里说清楚了),回滚顺序 JSONL → worktree → branch 是合理的;最危险的"子 apply 成功 + 父 pop 失败"分支专门留了 CRITICAL log 提示用户手动 git stash pop,这种"宁可留痕迹也别隐藏数据丢失"的取舍是对的。

/fork --clean 解析、FORK_ALLOWED_USERS 删除、FORK_ENABLED 默认开启都是干净的 follow-up;event-handler.ts--clean 的解析只允许出现在 description 之前,符合预期。

测试覆盖了场景 A 的核心五条路径(默认 WIP / --clean / 无 WIP / worktree 创建失败 / Feishu 失败回滚),mock 与真实 git 调用混合的方式合理。

发现

  1. 🟡 src/session/fork.ts:242 — 过时注释(confidence 95)
    catch 块里写"父工作树未被动过(stash create 不修改)",但本 PR 已经换成 stash push+pop 路径,描述与实现不一致;并且 CRITICAL 路径下父工作树其实是被动过的。属于 PR 改动直接引入的注释漂移,建议同步修正(见 inline 评论)。

  2. 🟡 src/session/tests/fork.test.ts:310 — 测试清理实际是 no-op(confidence 95)
    rmSync(${parentWorkdir}-fork-*, ...) —— fs.rmSync 不展开 glob,这是个字面路径,加 force:true 静默吞掉,fork 出的子 worktree 目录永远不会被清,会在 /tmp 慢慢堆积。建议改 readdir+filter(见 inline)。

验证过没问题的几点

  • 跨 worktree 的 stash apply <sha> 共享同一个 .git 的 stash refs,OK。
  • 失败回滚分支次序正确,不会出现 worktree 已删但分支仍在锁定的状态(worktree remove --force 配合 branch -D 兜底)。
  • --clean 路径完全跳过 stash 操作,父子状态零耦合。
  • FORK_ENABLED !== 'false' 默认开启的语义正确,不会因为变量未设置而误关。

不建议在本 PR 处理的更小颗粒(供参考)

  • shortId 4 hex 有冲突概率(约 1/65536),但 fork 是低频用户触发操作,接受;
  • gitCurrentBranch 在 detached HEAD 时会返回 HEAD,导致 HEAD-fork-xxx 分支名无效——边界场景,本 PR 不必处理。
    · Branch: feat/p0b-worktree-fork

Comment thread src/session/fork.ts Outdated
Comment thread src/session/__tests__/fork.test.ts Outdated
Comment thread src/session/fork.ts Outdated
Comment thread src/session/__tests__/fork.test.ts Outdated
- fork.ts: 更新 catch 注释,反映 push+pop 路径(原注释还说 stash create)
- fork.test.ts: afterEach 用 readdir+filter 真正清理 fork 子 worktree
  (rmSync 不支持 glob,原 '${path}-fork-*' 是字面量被静默吞掉)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

Claude finished @lishuceo's task in 1m 34s —— View job


Code Review — ✅ Approved (2 previous issues fixed)

  • Check previous review comments and whether issues are fixed
  • Read updated files (fork.ts, fork.test.ts)
  • Check for new issues introduced by latest commit (b81a0f2)
  • Reply to previous comments; threads已全部 resolved

复核结果

b81a0f2 干净地修掉了上轮提的两个 🟡 问题:

  1. src/session/fork.ts:242-245 — catch 注释已重写,准确描述 stash push -u + pop 路径,并把 CRITICAL 分支(子 apply 成功 / 父 pop 失败)的"父 WIP 仍卡在 stash@{0}"这层语义点出来,与上方 logger.error 的 CRITICAL log 形成呼应。✅

  2. src/session/__tests__/fork.test.ts:310-319 — 改成 readdir(tmpRoot) + filter(startsWith(\${baseName}-fork-`))`。验证了 path 拼装:

    • parentWorkdir = /tmp/fork-parent-XXXX
    • newWorkdir (fork.ts:104) = ${parent.workingDir}-fork-${shortId} = /tmp/fork-parent-XXXX-fork-YYYY
    • 清理 prefix fork-parent-XXXX-fork- 正好匹配。✅
    • existsSync(tmpRoot) 守卫合理(tmpRoot/tmp,实际不会缺,但写着也无害)。

没发现新问题

b81a0f2 只动了上述两处,改动范围窄,无其他副作用。整个 PR 的实现质量很好,可以合并。


· Branch: feat/p0b-worktree-fork

@lishuceo lishuceo merged commit 7ee4d1c into main May 27, 2026
4 checks passed
@lishuceo lishuceo deleted the feat/p0b-worktree-fork branch May 27, 2026 12:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant